Android画圆形图片,clippath方式/Xfermode方式 您所在的位置:网站首页 canvas clippath 抗锯齿 Android画圆形图片,clippath方式/Xfermode方式

Android画圆形图片,clippath方式/Xfermode方式

2023-09-15 07:14| 来源: 网络整理| 查看: 265

目录1.利用canvas.clipPath方法,按照自定义的Path图形去切割控件2.通过Xfermode方式实现

1.利用canvas.clipPath方法,按照自定义的Path图形去切割控件

ImageView显示图片,底层是通过Canvas将我们的图片资源画到View控件上实现的; 因此,要让其显示圆形图片,只需要对Canvas进行相应的变化,比如切割圆形、绘制圆形。

方法1:

描述:定义一个控件,包含ImageView,定义圆形Path路径,然后调用canvas.clipPath,将图片切割成圆形 缺点:锯齿

代码

import android.content.Context; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Path; import android.util.AttributeSet; import android.view.View; import android.widget.AbsoluteLayout; public class CircleImage extends AbsoluteLayout { // 也可以继承其他的父类 private int mWidth = 0; private int mHeight = 0; private Paint mPaint; private Path mPath; public CircleImage(Context context) { this(context, null); } public CircleImage(Context context, AttributeSet attrs) { this(context, attrs, 0); } public CircleImage(Context context, AttributeSet attrs, int defStyleAttr) { this(context, attrs, defStyleAttr, 0); } public CircleImage(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); initView(); /* ViewGroup默认情况下,出于性能考虑,会被设置成WILL_NOT_DROW,这样,ondraw就不会被执行了。 如果需要重写一个ViewGroup的onDraw方法,有两种方法: 1. 构造函数中,给ViewGroup设置一个颜色。 2. 构造函数中,调用setWillNotDraw(false),去掉其WILL_NOT_DRAW flag。 */ setWillNotDraw(false); } @Override protected void onFinishInflate() { super.onFinishInflate(); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); mWidth = getWidth(); mHeight = getHeight(); } private void initView() { mPaint = new Paint(); mPaint.setStyle(Paint.Style.STROKE); mPaint.setAntiAlias(true); mPath = new Path(); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); mPath.addCircle(mWidth / 2, mHeight / 2, mWidth / 2, Path.Direction.CCW); canvas.clipPath(mPath); } }

xml中定义

// ImageView中还可以设置src // android:src="@drawable/xxxpic",但需要设置android:scaleType="centerCrop" // 不然图片不会跟着窗口大小变化

方法2:

继承AppCompatImageView/ImageView自定义圆形控件 * canvas.clipPath:不支持硬件加速,所以在使用前需要禁止硬件加速 * setLayerType(View.LAYER_TYPE_SOFTWARE, null) * clipPath要在super.onDraw方法前,调用,否则无效(canvas已经被设置给View了) * 在onSizeChanged方法中,获取宽高 * 该这方法剪裁的是Canvas图形,View的实际形状是不变的,因此只能对src属性有效,对background属性是无效的

代码:

import android.content.Context; import android.graphics.Canvas; import android.graphics.Path; import android.util.AttributeSet; import android.content.res.TypedArray; import android.support.v7.widget.AppCompatImageView; import android.graphics.RectF; public class CircleImage extends AppCompatImageView { // 也可以继承ImageView做 private RectF mRect; private Path mPath; private float mRoundRadius; public CircleImage(Context context) { this(context, null); } public CircleImage(Context context, AttributeSet attrs) { this(context, attrs, -1); } public CircleImage(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); getAttributes(context, attrs); initView(context); } private void initView(Context context) { mRect = new RectF(); mPath = new Path(); setLayerType(LAYER_TYPE_SOFTWARE, null); // 禁用硬件加速 } private void getAttributes(Context context, AttributeSet attrs) { TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.CircleImage); mRoundRadius = ta.getInteger(R.styleable.CircleImage_roundRadius, -1); ta.recycle(); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); if (mRoundRadius < 0) { clipCircle(w, h); } else { clipRoundRect(w, h); } } /** * 圆角 */ private void clipRoundRect(int width, int height) { mRect.left = 0; mRect.top = 0; mRect.right = width; mRect.bottom = height; mPath.addRoundRect(mRect, mRoundRadius, mRoundRadius, Path.Direction.CW); } /** * 圆形 */ private void clipCircle(int width, int height) { int radius = Math.min(width, height)/2; mPath.addCircle(width/2, height/2, radius, Path.Direction.CW); } @Override protected void onDraw(Canvas canvas) { canvas.clipPath(mPath); super.onDraw(canvas); } }

attrs.xml

画面xml

解决锯齿的方法

1、通过Paint设置 Paint mPaint = new Paint(); mPaint.setFilterBitmap(true); 2、通过Canvas设置 canvas.setDrawFilter(new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG|Paint.FILTER_BITMAP_FLAG)); 不过这种方式不能完全解决圆形图片的锯齿 2.通过Xfermode方式实现

方法1:

自定义控件,包含一个ImageView对象,只对src属性设置的图片有效,background无效 该方法的注意事项就是:控件的大小和子child(ImageView)大小必须一致,可以不是正方形,原因是由于画完圆后,mImageView.setImageBitmap(roundBitmap);也需要设置一个圆盖上去,如果大小不一致,两个圆位置不一样,该问题待解决

代码:

package xxx; import android.content.Context; import android.util.AttributeSet; import android.util.Log; import android.view.View; import android.graphics.Paint; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.PorterDuffXfermode; import android.graphics.PorterDuff; import android.graphics.Rect; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.widget.AbsoluteLayout; import android.widget.ImageView; public class CircleImage extends AbsoluteLayout { private int mWidth = 100; private int mHeight = 100; private Paint mPaint; private String TAG = "PKG_UICTRL_CircleImage"; private ImageView mImageView = null; public CircleImage(Context context) { this(context, null); } public CircleImage(Context context, AttributeSet attrs) { this(context, attrs, 0); } public CircleImage(Context context, AttributeSet attrs, int defStyleAttr) { this(context, attrs, defStyleAttr, 0); } public CircleImage(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); mPaint = new Paint(); mPaint.setAntiAlias(true); mPaint.setFilterBitmap(true); mPaint.setDither(true); setWillNotDraw(false); } @Override protected void onFinishInflate() { super.onFinishInflate(); Log.d(TAG, "onFinishInflate: "); getView(); } private void getView() { Log.d(TAG, " getView iCount="+getChildCount()); for (int iCount = 0; iCount < getChildCount(); ++iCount) { View child = this.getChildAt(iCount); if (child instanceof ImageView) { Log.d(TAG, " ImageView"); mImageView=(ImageView)child; } } } @Override protected void onDraw(Canvas canvas) { if (null == mImageView){ return; } Drawable drawable = mImageView.getDrawable(); if (null != drawable && (drawable instanceof BitmapDrawable)) { Bitmap b = ((BitmapDrawable) drawable).getBitmap(); Bitmap bitmap = b.copy(Bitmap.Config.ARGB_8888, true); mWidth = getWidth(); mHeight = getHeight(); // 计算显示圆形的半径,为保证圆形,取图片的长宽小的一半作为圆形 int radius = (mWidth < mHeight ? mWidth : mHeight) / 2; // 获取我们处理后的圆形图片 Bitmap roundBitmap = getCroppedCircleBitmap(bitmap, radius); // 绘制图片进行显示 canvas.drawBitmap(roundBitmap, mWidth / 2 - radius, mHeight / 2 - radius, null); mImageView.setImageBitmap(roundBitmap); } else { super.onDraw(canvas); } } public Bitmap getCroppedCircleBitmap(Bitmap bmp, int radius) { Bitmap scaledSrcBmp; int diameter = radius * 2; // 对图片进行处理,获取我们需要的中央部分 Bitmap squareBitmap = getCenterBitmap(bmp); // 将图片缩放到需要的圆形比例大小 if (squareBitmap.getWidth() != diameter || squareBitmap.getHeight() != diameter) { scaledSrcBmp = Bitmap.createScaledBitmap(squareBitmap, diameter, diameter, true); } else { scaledSrcBmp = squareBitmap; } // 创建一个我们输出的对应 Bitmap output = Bitmap.createBitmap(scaledSrcBmp.getWidth(), scaledSrcBmp.getHeight(), Bitmap.Config.ARGB_8888); // 在output上进行绘画 Canvas canvas = new Canvas(output); // 创建裁剪的矩形 Rect rect = new Rect(0, 0, scaledSrcBmp.getWidth(),scaledSrcBmp.getHeight()); // 绘制dest目标区域 canvas.drawCircle(scaledSrcBmp.getWidth() / 2, scaledSrcBmp.getHeight() / 2, scaledSrcBmp.getWidth() / 2, mPaint); // 设置xfermode模式 mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); // 绘制src源区域 canvas.drawBitmap(scaledSrcBmp, rect, rect, mPaint); bmp.recycle(); squareBitmap.recycle(); scaledSrcBmp.recycle(); return output; } // 注意这里必须只能用bitmap.getWidth的图片,去算尺寸,不然外面的框,大于bitmap的大小,超出的就变成了黑色的 private Bitmap getCenterBitmap(Bitmap bitmap){ // 为了防止图片宽高不相等,造成圆形图片变形,因此截取长方形中处于中间位置最大的正方形图片 int bmpWidth = bitmap.getWidth(); int bmpHeight = bitmap.getHeight(); Bitmap squareBitmap; if (bmpHeight > bmpWidth) { // 截取正方形图片 ,从(bmpHeight - bmpWidth) / 2处开始截取 squareBitmap = Bitmap.createBitmap(bitmap, 0, (bmpHeight - bmpWidth) / 2, bmpWidth, bmpWidth); } else if (bmpHeight < bmpWidth) { squareBitmap = Bitmap.createBitmap(bitmap, (bmpWidth - bmpHeight) / 2, 0, bmpHeight, bmpHeight); } else { squareBitmap = bitmap; } return squareBitmap; } }

画面xml:

方法2:

自定义控件,继承ImageView,该控件本身就可以获得Drawable(this.getDrawable()),不需要像方法1,拿到mImageView,利用mImageView拿到Drawable(mImageView.getDrawable())

注意:

这两种只适合src设置的图片,原因是src设置的图片getDrawable()才不为空,不是点9图片拿到的是BitmapDrawable可以获取Bitmap图片,((BitmapDrawable) drawable).getBitmap()

点9图片和背景图片想要达到圆形图片,只需在方法1的代码中改onDraw

@Override protected void onDraw(Canvas canvas) { if (null == mImageView){ return; } mWidth = getWidth(); mHeight = getHeight(); Bitmap bitmap = initBitmap(); if (null != bitmap) { // Calculate radius of the circle. To ensure the circle, take half of the length or width as radius int radius = (mWidth < mHeight ? mWidth : mHeight) / 2; // get round Bitmap Bitmap roundBitmap = getCroppedCircleBitmap(bitmap, radius); // Draw Bitmap for display canvas.drawBitmap(roundBitmap, mWidth / 2 - radius, mHeight / 2 - radius, null); mImageView.setImageBitmap(roundBitmap); } else { super.onDraw(canvas); } } private Bitmap initBitmap() { Bitmap output = null; Drawable drawable1 = mImageView.getDrawable(); Drawable drawable2 = mImageView.getBackground(); Drawable drawable = drawable1==null ? drawable2 : drawable1; // 不能在构造方法中获取drawable,为null if (null != drawable && drawable instanceof BitmapDrawable) { output = ((BitmapDrawable) drawable).getBitmap(); } else if (null != drawable && drawable instanceof NinePatchDrawable) { //处理点9图片,利用canvas int width = drawable.getIntrinsicWidth(); // image original width int height = drawable.getIntrinsicHeight(); // image original height output = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(output); drawable.draw(canvas); } else if (null != drawable && drawable instanceof ColorDrawable){// 处理background设置的图片,没效果,待解决 output = Bitmap.createBitmap(mWidth, mHeight, Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(output); drawable.setBounds(0, 0, mWidth, mHeight); drawable.draw(canvas); } return output; }

方法3:

方法1,在ImageView是动态设置的情况下,调用完imageView的onDraw,圆形图片会失效 方法3,通过设置的bitmap画圆,自定义控件中不包含ImageView

代码示例:

package xxx; import android.content.Context; import android.util.AttributeSet; import android.util.Log; import android.view.View; import android.graphics.Paint; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.PorterDuffXfermode; import android.graphics.PorterDuff; import android.graphics.Rect; import android.widget.AbsoluteLayout; import android.view.ViewGroup; public class CircleImage extends AbsoluteLayout { // 也可以继承其他的父类 private String TAG = "TAG_CircleImage"; private int mWidth = 100; private int mHeight = 100; private int mRadius = 0; private Paint mPaint; private Bitmap mCircleImage; public CircleImage(Context context) { this(context, null); } public CircleImage(Context context, AttributeSet attrs) { this(context, attrs, 0); } public CircleImage(Context context, AttributeSet attrs, int defStyleAttr) { this(context, attrs, defStyleAttr, 0); } public CircleImage(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); mPaint = new Paint(); mPaint.setAntiAlias(true); mPaint.setFilterBitmap(true); mPaint.setDither(true); setWillNotDraw(false); // 关闭硬件加速 // 参考网站:https://juejin.im/post/6844903893189525512 // 参考网站:https://developer.android.com/guide/topics/graphics/hardware-accel this.setLayerType(LAYER_TYPE_SOFTWARE, mPaint); } @Override protected void onFinishInflate() { super.onFinishInflate(); Log.d(TAG, "onFinishInflate: "); // getView(); if (null != mCircleImage) { Log.d(TAG, "onFinishInflate: mCircleImage != null"); setCircleImageBitmap(mCircleImage); } } public void setCircleImageBitmap(Bitmap bmp) { Log.d(TAG, "setCircleImageBitmap"); mCircleImage = bmp; /*刷新,会触发调用onDraw*/ invalidate(); } private Bitmap updateImageBitmap(Bitmap bmp) { Log.d(TAG, "updateImageBitmap bmp = " + bmp); ViewGroup.LayoutParams layoutParams = this.getLayoutParams(); if (null != layoutParams && null != bmp) { mWidth = layoutParams.width; mHeight = layoutParams.height; Log.d(TAG, "updateImageBitmap layoutParams != null, mWidth = " + mWidth + ", mHeight = " + mHeight); // 计算显示圆形的半径,为保证圆形,取图片的长宽小的一半作为圆形 mRadius = (mWidth < mHeight ? mWidth : mHeight) / 2; Log.d(TAG, "updateImageBitmap mRadius = " + mRadius); // 获取我们处理后的圆形图片 if (mRadius > 0) { Log.d(TAG, "updateImageBitmap mRadius > 0"); Bitmap bitmap = getCroppedCircleBitmap(bmp, mRadius); return bitmap; } } return null; } @Override protected void onDraw(Canvas canvas) { Log.d(TAG, "onDraw"); if (null != mCircleImage) { Log.d(TAG, "onDraw, mCircleImage != null"); Bitmap bitmap = updateImageBitmap(mCircleImage); /* view.isHardwareAccelerated(): 只会受到Application、Activity的影响,如果Activity本身不支持硬件加速,那么返回false,反之返回true canvas.isHardwareAccelerated(): 会受到Application、Activity的影响,如果设置了setLayerType,那么会被setLayerType直接影响 */ Log.d(TAG, "onDraw, view HardwareAccelerated = " + this.isHardwareAccelerated() + ", Canvas HardwareAccelera = " + canvas.isHardwareAccelerated()); if (null != bitmap) { Log.d(TAG, "onDraw bitmap != null, bitmap width = " + bitmap.getWidth() + ", height = " + bitmap.getHeight()); // 绘制图片进行显示 canvas.drawBitmap(bitmap, mWidth / 2 - mRadius, mHeight / 2 - mRadius, null); /* 还原混合模式 */ mPaint.setXfermode(null); } } } public Bitmap getCroppedCircleBitmap(Bitmap bmp, int radius) { Bitmap scaledSrcBmp; int diameter = radius * 2; // 对图片进行处理,获取我们需要的中央部分 Bitmap squareBitmap = getCenterBitmap(bmp); // 将图片缩放到需要的圆形比例大小 if (squareBitmap.getWidth() != diameter || squareBitmap.getHeight() != diameter) { scaledSrcBmp = Bitmap.createScaledBitmap(squareBitmap, diameter, diameter, true); } else { scaledSrcBmp = squareBitmap; } // 创建一个我们输出的对应 Bitmap output = Bitmap.createBitmap(scaledSrcBmp.getWidth(), scaledSrcBmp.getHeight(), Bitmap.Config.ARGB_8888); // 在output上进行绘画 Canvas canvas = new Canvas(output); // 创建裁剪的矩形 Rect rect = new Rect(0, 0, scaledSrcBmp.getWidth(),scaledSrcBmp.getHeight()); // 绘制dest目标区域 canvas.drawCircle(scaledSrcBmp.getWidth() / 2, scaledSrcBmp.getHeight() / 2, scaledSrcBmp.getWidth() / 2, mPaint); // 设置xfermode模式 // 参考:https://www.jianshu.com/p/d11892bbe055 mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); // 绘制src源区域 canvas.drawBitmap(scaledSrcBmp, rect, rect, mPaint); /*注意:Bitmap.recycle 方法被弃用 在Android2.3之后,整个Bitmap,包括数据和引用,都放在了堆中,这样,整个Bitmap的回收就全部交给GC了,这个recycle方法就再也不需要使用了。 */ // bmp.recycle(); // squareBitmap.recycle(); //scaledSrcBmp.recycle(); return output; } // 注意这里必须只能用bitmap.getWidth的图片,去算尺寸,不然外面的框,大于bitmap的大小,超出的就变成了黑色的 private Bitmap getCenterBitmap(Bitmap bitmap) { // 为了防止图片宽高不相等,造成圆形图片变形,因此截取长方形中处于中间位置最大的正方形图片 int bmpWidth = bitmap.getWidth(); int bmpHeight = bitmap.getHeight(); Log.d(TAG, "getCenterBitmap bmpWidth = "+bmpWidth+", bmpHeight = "+bmpHeight); Bitmap squareBitmap; if (bmpHeight > bmpWidth) { // 截取正方形图片 ,从(bmpHeight - bmpWidth) / 2处开始截取 squareBitmap = Bitmap.createBitmap(bitmap, 0, (bmpHeight - bmpWidth) / 2, bmpWidth, bmpWidth); } else if (bmpHeight < bmpWidth) { squareBitmap = Bitmap.createBitmap(bitmap, (bmpWidth - bmpHeight) / 2, 0, bmpHeight, bmpHeight); } else { squareBitmap = bitmap; } return squareBitmap; } }

其他方法参考 https://segmentfault.com/a/1190000012253911 https://blog.csdn.net/Mr_dsw/article/details/48629177

根据资源拿到bitmap图片的一个方法为

Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.pic);


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有